using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using Microformats.ConfigurationSections;
using Microformats.Entities;
using Microformats.Interfaces;
using Microformats.Readers.Entities;
using Microformats.Readers.Interfaces;

namespace Microformats.Readers
{
    public enum InputSourceType
    {
        Html,
        Xml
    }

    /// <summary>
    /// Represents a foreward only Microformat reader.
    /// </summary>
    public class MicroformatReader : IMicroformatReader
    {
        private IMicroformatReader readerType;
        private InputSourceType currentSourceType;
        private string baseAddress;


        /// <summary>
        /// Gets the type of the current source.
        /// </summary>
        /// <value>The type of the current source.</value>
        internal InputSourceType CurrentSourceType
        {
            get { return currentSourceType; }
        }

        /// <summary>
        /// A list of microformats that the reader implementation will have to find.
        /// </summary>
        internal List<MicroformatInformation> microformatsToFind;

        /// <summary>
        /// Gets the Actual Implementation of the Reader.
        /// </summary>
        public IMicroformatReader Reader
        {
            get { return readerType; }
        }

        /// <summary>
        /// Gets or sets the base address.
        /// </summary>
        /// <value>The base address.</value>
        internal string BaseAddress
        {
            get { return baseAddress; }
            set { baseAddress = value; }
        }

        /// <summary>
        /// Creates an instance of MicroformatReader using a given stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="sourceType">Type of the source.</param>
        /// <param name="baseAddress">The base adress of the stream</param>
        public MicroformatReader(Stream stream, InputSourceType sourceType, string baseAddress)
            : this(stream, sourceType)
        {
            this.baseAddress = baseAddress;
        }

        /// <summary>
        /// Creates an instance of MicroformatReader using a given stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="sourceType">Type of the source.</param>
        /// <param name="baseAddress">The base adress of the text reader.</param>
        public MicroformatReader(TextReader stream, InputSourceType sourceType, string baseAddress)
            : this(stream, sourceType)
        {
            this.baseAddress = baseAddress;
        }

        /// <summary>
        /// Creates an instance of MicroformatReader using a given stream.
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="sourceType">Type of the source.</param>
        public MicroformatReader(Stream stream, InputSourceType sourceType)
            : this()
        {
            currentSourceType = sourceType;

            if (sourceType == InputSourceType.Xml)
            {
                readerType = new MicroformatStreamReader(stream, this);
            }
            else
            {
                readerType = new MicroformatHtmlReader(stream, this);
            }
        }

        public MicroformatReader(TextReader textReader, InputSourceType sourceType)
            : this()
        {
            currentSourceType = sourceType;

            if (sourceType == InputSourceType.Xml)
            {
                readerType = new MicroformatStreamReader(textReader, this);
            }
            else
            {
                readerType = new MicroformatHtmlReader(textReader, this);
            }
        }

        public MicroformatReader()
        {
            if(baseAddress == null)
            {
                baseAddress = String.Empty;
            }

            microformatsToFind = new List<MicroformatInformation>();

            MicroformatConfigSection mcs = (MicroformatConfigSection) ConfigurationManager.GetSection("MicroformatsSection");

            if (mcs.Microformats == null)
                throw new ConfigurationErrorsException("A Microformat configuration section must exist with at least one configured microformat to parse");

            //Parse the configuration and create an action plan of items that we should be searching for.
            foreach (MicroformatElement me in mcs.Microformats)
            {
                MicroformatInformation mi = new MicroformatInformation(null); //There is no asscoiated parent field.

                mi.Root = me.Root;
                mi.RootType = me.RootType;
                mi.Name = me.Name;
                mi.DataType = me.DataType;
                mi.Fields = new List<ICommonInformation>();

                if (me.Fields.Count > 0)
                {
                    ParseFieldInformation(me.Fields, mi.Fields, mi);
                }

                microformatsToFind.Add(mi);
            }
        }

        /// <summary>
        /// Parses the Field Configuration Section information.
        /// </summary>
        /// <param name="fc">The collection of fields in the configuration.</param>
        /// <param name="f">The mi.</param>
        /// <param name="parentCommonInformation">The parent common information.</param>
        private static void ParseFieldInformation(FieldCollection fc, List<ICommonInformation> f, ICommonInformation parentCommonInformation)
        {
            foreach (FieldElement currentField in fc)
            {
                //Create and add the field into the correct collection.
                //Potentially make this a factory.
                FieldInformation newFieldInformation = new FieldInformation(parentCommonInformation);
                newFieldInformation.DataType = currentField.DataType;
                newFieldInformation.Name = currentField.Name;
                newFieldInformation.RootType = currentField.RootType;
                newFieldInformation.Plurality = currentField.Plurality;
                newFieldInformation.Fields = new List<ICommonInformation>();
                f.Add(newFieldInformation);

                //We have a nested microformat so we need to parse that and it's fields.
                if (currentField.DataType == "Microformat")
                {
                    //Get the Micforomat
                    MicroformatConfigSection mcs = (MicroformatConfigSection) ConfigurationManager.GetSection("MicroformatsSection");
                    MicroformatElement me = mcs.Microformats[currentField.Name];
                    ParseMicroformatInformation(me, newFieldInformation);
                }

                if (currentField.Fields.Count > 0)
                {
                    ParseFieldInformation(currentField.Fields, newFieldInformation.Fields, newFieldInformation);
                }
            }
        }

        /// <summary>
        /// Parses the microformat information.
        /// </summary>
        /// <param name="me">The Microformat Configuration Element.</param>
        /// <param name="f">The f.</param>
        private static void ParseMicroformatInformation(MicroformatElement me, ICommonInformation f)
        {
            MicroformatInformation mi = new MicroformatInformation(f);

            mi.Root = me.Root;
            mi.RootType = me.RootType;
            mi.Name = me.Type;
            mi.Fields = new List<ICommonInformation>();

            if (me.Fields.Count > 0)
            {
                ParseFieldInformation(me.Fields, f.Fields, mi);
            }

            f.Fields.Add(mi);
        }

        /// <summary>
        /// Reads a microformat from the source stream
        /// </summary>
        /// <returns></returns>
        public Microformat Read()
        {
            return readerType.Read();
        }

        #region IDisposable Members

        public void Dispose()
        {
            //WE will not dispose the stream because it might be needed by the outer context.
        }

        #endregion
    }
}